﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace NES
{
    internal class Debugger
    {
        private string LogFile;
        private int ClockCycles;
        private bool Branch;
        private bool PageCross;

        private const int BASE_TIME = 3;
        private enum AM { ABS, ABX, ABX_NC, ABY, ABY_NC, ACC, IMM, IMP, IND, INX, INY, INY_NC, REL, ZEP, ZPX, ZPY, UNK }
        private enum Flag { N = 0x80, V = 0x40, P = 0x20, B = 0x10, D = 0x08, I = 0x04, Z = 0x02, C = 0x01 }

        public string[] OpcodeNames =
        {
        //      0      1      2      3      4      5      6      7      8      9      A      B      C      D      E      F
        /* 0 */ "???", "ORA", "???", "???", "???", "ORA", "ASL", "???", "PHP", "ORA", "ASL", "???", "???", "ORA", "ASL", "???",
        /* 1 */ "BPL", "ORA", "???", "???", "???", "ORA", "ASL", "???", "CLC", "ORA", "???", "???", "???", "ORA", "ASL", "???",
        /* 2 */ "JSR", "AND", "???", "???", "BIT", "AND", "ROL", "???", "PLP", "AND", "ROL", "???", "BIT", "AND", "ROL", "???",
        /* 3 */ "BMI", "AND", "???", "???", "???", "AND", "ROL", "???", "SEC", "AND", "???", "???", "???", "AND", "ROL", "???",
        /* 4 */ "RTI", "EOR", "???", "???", "???", "EOR", "LSR", "???", "PHA", "EOR", "LSR", "???", "JMP", "EOR", "LSR", "???",
        /* 5 */ "BVC", "EOR", "???", "???", "???", "EOR", "LSR", "???", "???", "EOR", "???", "???", "???", "EOR", "LSR", "???",
        /* 6 */ "RTS", "ADC", "???", "???", "???", "ADC", "ROR", "???", "PLA", "ADC", "ROR", "???", "JMP", "ADC", "ROR", "???",
        /* 7 */ "BVS", "ADC", "???", "???", "???", "ADC", "ROR", "???", "SEI", "ADC", "???", "???", "???", "ADC", "ROR", "???",
        /* 8 */ "???", "STA", "???", "???", "STY", "STA", "STX", "???", "DEY", "???", "TXA", "???", "STY", "STA", "STX", "???",
        /* 9 */ "BCC", "STA", "???", "???", "STY", "STA", "STX", "???", "TYA", "STA", "TXS", "???", "???", "STA", "???", "???",
        /* A */ "LDY", "LDA", "LDX", "???", "LDY", "LDA", "LDX", "???", "TAY", "LDA", "TAX", "???", "LDY", "LDA", "LDX", "???",
        /* B */ "BCS", "LDA", "???", "???", "LDY", "LDA", "LDX", "???", "CLV", "LDA", "TSX", "???", "LDY", "LDA", "LDX", "???",
        /* C */ "CPY", "CMP", "???", "???", "CPY", "CMP", "DEC", "???", "INY", "CMP", "DEX", "???", "CPY", "CMP", "DEC", "???",
        /* D */ "BNE", "CMP", "???", "???", "???", "CMP", "DEC", "???", "CLD", "CMP", "???", "???", "???", "CMP", "DEC", "???",
        /* E */ "CPX", "SBC", "???", "???", "CPX", "SBC", "INC", "???", "INX", "SBC", "NOP", "???", "CPX", "SBC", "INC", "???",
        /* F */ "BEQ", "SBC", "???", "???", "???", "SBC", "INC", "???", "SED", "SBC", "???", "???", "???", "SBC", "INC", "???",
        };

        private AM[] OpcodeAddressingModes =
        {
        //      0       1       2       3       4       5       6       7       8       9          A       B       C       D          E          F
        /* 0 */ AM.UNK, AM.INX, AM.UNK, AM.UNK, AM.UNK, AM.ZEP, AM.ZEP, AM.UNK, AM.IMP, AM.IMM,    AM.ACC, AM.UNK, AM.UNK, AM.ABS,    AM.ABS,    AM.UNK,
        /* 1 */ AM.REL, AM.INY, AM.UNK, AM.UNK, AM.UNK, AM.ZPX, AM.ZPX, AM.UNK, AM.IMP, AM.ABY,    AM.UNK, AM.UNK, AM.UNK, AM.ABX,    AM.ABX_NC, AM.UNK,
        /* 2 */ AM.ABS, AM.INX, AM.UNK, AM.UNK, AM.ZEP, AM.ZEP, AM.ZEP, AM.UNK, AM.IMP, AM.IMM,    AM.ACC, AM.UNK, AM.ABS, AM.ABS,    AM.ABS,    AM.UNK,
        /* 3 */ AM.REL, AM.INY, AM.UNK, AM.UNK, AM.UNK, AM.ZPX, AM.ZPX, AM.UNK, AM.IMP, AM.ABY,    AM.UNK, AM.UNK, AM.UNK, AM.ABX,    AM.ABX_NC, AM.UNK,
        /* 4 */ AM.IMP, AM.INX, AM.UNK, AM.UNK, AM.UNK, AM.ZEP, AM.ZEP, AM.UNK, AM.IMP, AM.IMM,    AM.ACC, AM.UNK, AM.ABS, AM.ABS,    AM.ABS,    AM.UNK,
        /* 5 */ AM.REL, AM.INY, AM.UNK, AM.UNK, AM.UNK, AM.ZPX, AM.ZPX, AM.UNK, AM.IMP, AM.ABY,    AM.UNK, AM.UNK, AM.UNK, AM.ABX,    AM.ABX_NC, AM.UNK,
        /* 6 */ AM.IMP, AM.INX, AM.UNK, AM.UNK, AM.UNK, AM.ZEP, AM.ZEP, AM.UNK, AM.IMP, AM.IMM,    AM.ACC, AM.UNK, AM.IND, AM.ABS,    AM.ABS,    AM.UNK,
        /* 7 */ AM.REL, AM.INY, AM.UNK, AM.UNK, AM.UNK, AM.ZPX, AM.ZPX, AM.UNK, AM.IMP, AM.ABY,    AM.UNK, AM.UNK, AM.UNK, AM.ABX,    AM.ABX_NC, AM.UNK,
        /* 8 */ AM.UNK, AM.INX, AM.UNK, AM.UNK, AM.ZEP, AM.ZEP, AM.ZEP, AM.UNK, AM.IMP, AM.UNK,    AM.IMP, AM.UNK, AM.ABS, AM.ABS,    AM.ABS,    AM.UNK,
        /* 9 */ AM.REL, AM.INY, AM.UNK, AM.UNK, AM.ZPX, AM.ZPX, AM.ZPY, AM.UNK, AM.IMP, AM.ABY_NC, AM.IMP, AM.UNK, AM.UNK, AM.ABX_NC, AM.UNK,    AM.UNK,
        /* A */ AM.IMM, AM.INX, AM.IMM, AM.UNK, AM.ZEP, AM.ZEP, AM.ZEP, AM.UNK, AM.IMP, AM.IMM,    AM.IMP, AM.UNK, AM.ABS, AM.ABS,    AM.ABS,    AM.UNK,
        /* B */ AM.REL, AM.INY, AM.UNK, AM.UNK, AM.ZPX, AM.ZPX, AM.ZPY, AM.UNK, AM.IMP, AM.ABY,    AM.IMP, AM.UNK, AM.ABX, AM.ABX,    AM.ABY,    AM.UNK,
        /* C */ AM.IMM, AM.INX, AM.UNK, AM.UNK, AM.ZEP, AM.ZEP, AM.ZEP, AM.UNK, AM.IMP, AM.IMM,    AM.IMP, AM.UNK, AM.ABS, AM.ABS,    AM.ABS,    AM.UNK,
        /* D */ AM.REL, AM.INY, AM.UNK, AM.UNK, AM.UNK, AM.ZPX, AM.ZPX, AM.UNK, AM.IMP, AM.ABY,    AM.UNK, AM.UNK, AM.UNK, AM.ABX,    AM.ABX_NC, AM.UNK,
        /* E */ AM.IMM, AM.INX, AM.UNK, AM.UNK, AM.ZEP, AM.ZEP, AM.ZEP, AM.UNK, AM.IMP, AM.IMM,    AM.IMP, AM.UNK, AM.ABS, AM.ABS,    AM.ABS,    AM.UNK,
        /* F */ AM.REL, AM.INY, AM.UNK, AM.UNK, AM.UNK, AM.ZPX, AM.ZPX, AM.UNK, AM.IMP, AM.ABY,    AM.UNK, AM.UNK, AM.UNK, AM.ABX,    AM.ABX_NC, AM.UNK,
        };

        public Debugger()
        {
            LogFile = @"C:\wamp\www\jahnes\debug.txt";
            ClockCycles = 0;
            Branch = false;
            PageCross = false;

            using (FileStream FS = new FileStream(LogFile, FileMode.Create, FileAccess.Write)) { }
        }

        public void Log(string Input)
        {
            using (FileStream FS = new FileStream(LogFile, FileMode.Append, FileAccess.Write))
            {
                using (StreamWriter Writer = new StreamWriter(FS))
                {
                    Writer.WriteLine(Input);
                }
            }
        }

        public void Debug(byte Opcode, M6502 Context, byte[] RAM)
        {
            string debug = string.Format("{0:X4}", Context.P.PC.W) + "  " + string.Format("{0:X2}", Opcode) + " ";

            switch (OpcodeAddressingModes[Opcode])
            {
                case AM.ABS: debug += AddressingAbsolute(Opcode, Context, RAM); break;
                case AM.ABX: debug += AddressingAbsoluteX(Opcode, Context, RAM); break;
                case AM.ABX_NC: debug += AddressingAbsoluteX_NoCross(Opcode, Context, RAM); break;
                case AM.ABY: debug += AddressingAbsoluteY(Opcode, Context, RAM); break;
                case AM.ABY_NC: debug += AddressingAbsoluteY_NoCross(Opcode, Context, RAM); break;
                case AM.ACC: debug += AddressingAccumulator(Opcode); break;
                case AM.IMM: debug += AddressingImmediate(Opcode, Context, RAM); break;
                case AM.IMP: debug += AddressingImplied(Opcode); break;
                case AM.IND: debug += AddressingIndirect(Opcode, Context, RAM); break;
                case AM.INX: debug += AddressingIndirectX(Opcode, Context, RAM); break;
                case AM.INY: debug += AddressingIndirectY(Opcode, Context, RAM); break;
                case AM.REL: debug += AddressingRelative(Opcode, Context, RAM); break;
                case AM.ZEP: debug += AddressingZeroPage(Opcode, Context, RAM); break;
                case AM.ZPX: debug += AddressingZeroPageX(Opcode, Context, RAM); break;
                case AM.ZPY: debug += AddressingZeroPageY(Opcode, Context, RAM); break;
            }

            debug += "A:" + string.Format("{0:X2}", Context.P.A);
            debug += " X:" + string.Format("{0:X2}", Context.P.X);
            debug += " Y:" + string.Format("{0:X2}", Context.P.Y);
            debug += " P:" + string.Format("{0:X2}", Context.P.F);
            debug += " SP:" + string.Format("{0:X2}", Context.P.SP);
            debug += " CYC:" + string.Format("{0,3}", ClockCycles);

            ClockCycles += Commons.OpcodeCycles[Opcode] * BASE_TIME;
            if (Branch)
            {
                ClockCycles += BASE_TIME;
                Branch = false;
            }
            if (PageCross)
            {
                ClockCycles += BASE_TIME;
                PageCross = false;
            }

            if (ClockCycles >= 341) ClockCycles -= 341;

            Log(debug);
        }

        private string AddressingAbsolute(byte Opcode, M6502 Context, byte[] RAM)
        {
            string first_operand = string.Format("{0:X2}", RAM[Context.P.PC.W + 1]);
            string second_operand = string.Format("{0:X2}", RAM[Context.P.PC.W + 2]);

            string opcode_name = OpcodeNames[Opcode];
            string value = string.Empty;
            if (new string[] { "JMP", "JSR" }.Contains(opcode_name)) value = "                       ";
            else
            {
                Commons.PairRegister temp = new Commons.PairRegister();
                temp.L = RAM[(ushort)(Context.P.PC.W + 1)];
                temp.H = RAM[(ushort)(Context.P.PC.W + 2)];
                value = " = " + string.Format("{0:X2}", RAM[temp.W]) + "                  ";
            }

            return first_operand + " " + second_operand + "  " + opcode_name + " $" + second_operand + first_operand + value;
        }

        private string AddressingAbsoluteX(byte Opcode, M6502 Context, byte[] RAM)
        {
            Commons.PairRegister temp = new Commons.PairRegister();
            temp.L = RAM[Context.P.PC.W + 1];
            temp.H = RAM[Context.P.PC.W + 2];
            PageCross = (temp.H != (byte)((temp.W + Context.P.X) >> 8));

            return string.Format("{0:X2}", temp.L) + " " + string.Format("{0:X2}", temp.H) + "  " + OpcodeNames[Opcode] + " $" + string.Format("{0:X4}", temp.W) + ",X @ " + string.Format("{0:X4}", (ushort)(temp.W + Context.P.X)) + " = " + string.Format("{0:X2}", RAM[(ushort)(temp.W + Context.P.X)]) + "         ";
        }

        private string AddressingAbsoluteX_NoCross(byte Opcode, M6502 Context, byte[] RAM)
        {
            Commons.PairRegister temp = new Commons.PairRegister();
            temp.L = RAM[Context.P.PC.W + 1];
            temp.H = RAM[Context.P.PC.W + 2];

            return string.Format("{0:X2}", temp.L) + " " + string.Format("{0:X2}", temp.H) + "  " + OpcodeNames[Opcode] + " $" + string.Format("{0:X4}", temp.W) + ",X @ " + string.Format("{0:X4}", (ushort)(temp.W + Context.P.X)) + " = " + string.Format("{0:X2}", RAM[(ushort)(temp.W + Context.P.X)]) + "         ";
        }

        private string AddressingAbsoluteY(byte Opcode, M6502 Context, byte[] RAM)
        {
            Commons.PairRegister temp = new Commons.PairRegister();
            temp.L = RAM[Context.P.PC.W + 1];
            temp.H = RAM[Context.P.PC.W + 2];
            PageCross = (temp.H != (byte)((temp.W + Context.P.Y) >> 8));

            return string.Format("{0:X2}", temp.L) + " " + string.Format("{0:X2}", temp.H) + "  " + OpcodeNames[Opcode] + " $" + string.Format("{0:X4}", temp.W) + ",Y @ " + string.Format("{0:X4}", (ushort)(temp.W + Context.P.Y)) + " = " + string.Format("{0:X2}", RAM[(ushort)(temp.W + Context.P.Y)]) + "         ";
        }

        private string AddressingAbsoluteY_NoCross(byte Opcode, M6502 Context, byte[] RAM)
        {
            Commons.PairRegister temp = new Commons.PairRegister();
            temp.L = RAM[Context.P.PC.W + 1];
            temp.H = RAM[Context.P.PC.W + 2];

            return string.Format("{0:X2}", temp.L) + " " + string.Format("{0:X2}", temp.H) + "  " + OpcodeNames[Opcode] + " $" + string.Format("{0:X4}", temp.W) + ",Y @ " + string.Format("{0:X4}", (ushort)(temp.W + Context.P.Y)) + " = " + string.Format("{0:X2}", RAM[(ushort)(temp.W + Context.P.Y)]) + "         ";
        }

        private string AddressingAccumulator(byte Opcode)
        {
            return "       " + OpcodeNames[Opcode] + " A                           ";
        }

        private string AddressingImmediate(byte Opcode, M6502 Context, byte[] RAM)
        {
            string first_operand = string.Format("{0:X2}", RAM[(ushort)(Context.P.PC.W + 1)]);

            return first_operand + "     " + OpcodeNames[Opcode] + " #$" + first_operand + "                        ";
        }

        private string AddressingImplied(byte Opcode)
        {
            return "       " + OpcodeNames[Opcode] + "                             ";
        }

        private string AddressingIndirect(byte Opcode, M6502 Context, byte[] RAM)
        {
            Commons.PairRegister temp = new Commons.PairRegister();
            temp.L = RAM[(ushort)(Context.P.PC.W + 1)];
            temp.H = RAM[(ushort)(Context.P.PC.W + 2)];

            return string.Format("{0:X2}", temp.L) + " " + string.Format("{0:X2}", temp.H) + "  " + OpcodeNames[Opcode] + " ($" + string.Format("{0:X4}", temp.W) + ") = " + string.Format("{0:X2}", RAM[temp.W + 1]) + string.Format("{0:X2}", RAM[(ushort)(temp.W)]) + "              ";
        }

        private string AddressingIndirectX(byte Opcode, M6502 Context, byte[] RAM)
        {
            Commons.PairRegister temp = new Commons.PairRegister();
            temp.L = (byte)(RAM[(ushort)(Context.P.PC.W + 1)] + Context.P.X);
            temp.L = RAM[temp.L];
            temp.H = (byte)(RAM[(ushort)(Context.P.PC.W + 1)] + Context.P.X + 1);
            temp.H = RAM[temp.H];
            byte operand = RAM[(ushort)(Context.P.PC.W + 1)];

            return string.Format("{0:X2}", operand) + "     " + OpcodeNames[Opcode] + " ($" + string.Format("{0:X2}", operand) + ",X) @ " + string.Format("{0:X2}", (byte)(operand + Context.P.X)) + " = " + string.Format("{0:X4}", (ushort)(temp.W)) + " = " + string.Format("{0:X2}", RAM[temp.W]) + "    ";
        }

        private string AddressingIndirectY(byte Opcode, M6502 Context, byte[] RAM)
        {
            Commons.PairRegister temp = new Commons.PairRegister();
            temp.L = RAM[(ushort)(Context.P.PC.W + 1)];
            temp.L = RAM[temp.L];
            temp.H = (byte)(RAM[(ushort)(Context.P.PC.W + 1)] + 1);
            temp.H = RAM[temp.H];
            byte operand = RAM[(ushort)(Context.P.PC.W + 1)];
            PageCross = (temp.H != (byte)((temp.W + Context.P.Y) >> 8));

            return string.Format("{0:X2}", operand) + "     " + OpcodeNames[Opcode] + " ($" + string.Format("{0:X2}", operand) + "),Y = " + string.Format("{0:X4}", temp.W) + " @ " + string.Format("{0:X4}", (ushort)(temp.W + Context.P.Y)) + " = " + string.Format("{0:X2}", RAM[(ushort)(temp.W + Context.P.Y)]) + "  ";
        }

        private string AddressingRelative(byte Opcode, M6502 Context, byte[] RAM)
        {
            byte before = (byte)((Context.P.PC.W + 2) >> 8);
            short disp = RAM[(ushort)(Context.P.PC.W + 1)];
            Context.P.PC.W = (ushort)(Context.P.PC.W + disp);

            switch (Opcode)
            {
                case 0x10: Branch = !TestFlag(Context, Flag.N); break;
                case 0x30: Branch = TestFlag(Context, Flag.N); break;
                case 0x50: Branch = !TestFlag(Context, Flag.V); break;
                case 0x70: Branch = TestFlag(Context, Flag.V); break;
                case 0x90: Branch = !TestFlag(Context, Flag.C); break;
                case 0xB0: Branch = TestFlag(Context, Flag.C); break;
                case 0xD0: Branch = !TestFlag(Context, Flag.Z); break;
                case 0xF0: Branch = TestFlag(Context, Flag.Z); break;
            }
            if (Branch) PageCross = (before != Context.P.PC.H);

            Context.P.PC.W += 2;

            return string.Format("{0:X2}", disp & 0xFF) + "     " + OpcodeNames[Opcode] + " $" + string.Format("{0:X4}", Context.P.PC.W) + "                       ";
        }

        private string AddressingZeroPage(byte Opcode, M6502 Context, byte[] RAM)
        {
            string first_operand = string.Format("{0:X2}", RAM[(ushort)(Context.P.PC.W + 1)]);
            string first_address = string.Format("{0:X2}", RAM[RAM[(ushort)(Context.P.PC.W + 1)]]);

            return first_operand + "     " + OpcodeNames[Opcode] + " $" + first_operand + " = " + first_address + "                    ";
        }

        private string AddressingZeroPageX(byte Opcode, M6502 Context, byte[] RAM)
        {
            byte operand = RAM[(ushort)(Context.P.PC.W + 1)];

            return string.Format("{0:X2}", operand) + "     " + OpcodeNames[Opcode] + " $" + string.Format("{0:X2}", operand) + ",X @ " + string.Format("{0:X2}", (byte)(operand + Context.P.X)) + " = " + string.Format("{0:X2}", RAM[(byte)(operand + Context.P.X)]) + "             ";
        }

        private string AddressingZeroPageY(byte Opcode, M6502 Context, byte[] RAM)
        {
            byte operand = RAM[(ushort)(Context.P.PC.W + 1)];

            return string.Format("{0:X2}", operand) + "     " + OpcodeNames[Opcode] + " $" + string.Format("{0:X2}", operand) + ",Y @ " + string.Format("{0:X2}", (byte)(operand + Context.P.Y)) + " = " + string.Format("{0:X2}", RAM[(byte)(operand + Context.P.Y)]) + "             ";
        }

        private bool TestFlag(M6502 Context, Flag Flag)
        {
            byte value = (byte)Flag;
            return ((Context.P.F & value) == value);
        }
    }
}
